package application.dynamic;

import lombok.Data;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

public class JarLoader {

    public static class ClassLoaderManager {
        Map<String, ClassLeaderInformation> classLoaders;

        private static ClassLoaderManager classLoaderManager = new ClassLoaderManager();

        private ClassLoaderManager() {
            classLoaders = new HashMap<>();
        }

        public static ClassLoaderManager getInstance() {
            if (classLoaderManager == null) {
                classLoaderManager = new ClassLoaderManager();
            }
            return classLoaderManager;
        }

        public void registerClassLoader(String name, ClassLeaderInformation classLeaderInformation) {
            this.classLoaders.put(name, classLeaderInformation);
        }

        public ClassLeaderInformation getURLClassLoader(String name) {
            return this.classLoaders.get(name);
        }

        public void release(String registerName) {
            ClassLeaderInformation classLeaderInformation = this.classLoaders.get(registerName);
            try {
                classLeaderInformation.urlClassLoader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            this.classLoaders.remove(registerName);
        }

        public boolean isEmpty() {
            return this.classLoaders.isEmpty();
        }

        public Map<String, ClassLeaderInformation> lookup() {
            return this.classLoaders;
        }
    }

    @Data
    public static class ClassLeaderInformation {
        private String mainJarPath;

        private URLClassLoader urlClassLoader;
    }

    private Manifest init(String jarPath) throws IOException {
        if (jarPath == null || !new File(jarPath).isFile()) {
            return null;
        }
        JarFile jarFile = new JarFile(jarPath);
        return jarFile.getManifest();
    }

    private URLClassLoader initClassLoader(String registerName, String jarPath) throws IOException {
        Attributes mainAttributes = init(jarPath).getMainAttributes();
        String classPath = mainAttributes.getValue("Class-Path");
        String[] split = classPath.split("\\s+");
        URL[] urls = new URL[split.length + 1];
        for (int i = 0; i < split.length; i++) {
            String path = split[i];
            path = "file://".concat(path);
            urls[i] = new File(path).toURI().toURL();
        }
        ClassLeaderInformation classLeaderInformation = new ClassLeaderInformation();
        classLeaderInformation.mainJarPath = "file://".concat(jarPath);
        urls[urls.length - 1] = new File(classLeaderInformation.mainJarPath).toURI().toURL();
        URLClassLoader urlClassLoader = new URLClassLoader(urls);
        classLeaderInformation.urlClassLoader = urlClassLoader;
        ClassLoaderManager.getInstance().registerClassLoader(registerName, classLeaderInformation);
        return urlClassLoader;
    }

    public static <T> T executeMethod(Method method, boolean isClose, Object object, Object... objects) {
        method.setAccessible(true);
        try {
            return (T) method.invoke(object, objects);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static synchronized void release(String registerName) {
        ClassLoaderManager.getInstance().release(registerName);
    }
}
